﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace Server
{
    /// <summary>
    /// Contains the various mandatory informations related to the server connection.
    /// </summary>
    internal struct ConnectionInformations
    {
        #region Fields

        /// <summary>
        /// The connection address.
        /// </summary>
        private readonly string m_address;

        /// <summary>
        /// The connection port.
        /// </summary>
        /// <remarks>Port must be between <see cref="F:System.Net.IPEndPoint.MinPort" /> and <see cref="F:System.Net.IPEndPoint.MaxPort" />.</remarks>
        private readonly int m_port;

        /// <summary>
        /// The current version of the client software.
        /// </summary>
        private readonly string m_clientVersion;

        /// <summary>
        /// The address to use in order to update the client software.
        /// </summary>
        private readonly string m_updateAddress;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="T:Server.ConnectionInformations" /> class.
        /// </summary>
        /// <param name="address">The connection address.</param>
        /// <param name="port">The connection port.</param>
        /// <param name="clientVersion">The current version of the client software.</param>
        /// <param name="updateAddress">The address to use in order to update the client software.</param>
        internal ConnectionInformations(string address, int port, string clientVersion, string updateAddress)
        {
            Contract.Requires<ArgumentNullException>(!String.IsNullOrWhiteSpace(address));
            Contract.Requires<ArgumentOutOfRangeException>(port >= IPEndPoint.MinPort && port <= IPEndPoint.MaxPort);
            Contract.Requires<ArgumentNullException>(!String.IsNullOrWhiteSpace(clientVersion));
            Contract.Requires<ArgumentNullException>(!String.IsNullOrWhiteSpace(updateAddress));

            m_address = address;
            m_port = port;
            m_clientVersion = clientVersion;
            m_updateAddress = updateAddress;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:Server.ConnectionInformations" /> class.
        /// </summary>
        /// <param name="reader">Data reader.</param>
        /// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached.</exception>
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed.</exception>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="T:System.IO.InvalidDataException">The server address read was null or empty, or if value consists exclusively of white-space characters -or- the port read was lower than the minimum value or greater than the maximum value that can be assigned to a port -or- the client version read was null or empty, or if value consists exclusively of white-space characters -or- the update address read was null or empty, or if value consists exclusively of white-space characters.</exception>
        internal ConnectionInformations(BinaryReader reader)
        {
            Contract.Requires<ArgumentNullException>(reader != null);

            var address = reader.ReadString();
            if (String.IsNullOrWhiteSpace(address))
                throw new InvalidDataException("The server address read was null or empty, or if value consists exclusively of white-space characters.");
            m_address = address;

            var port = reader.ReadInt32();
            if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
                throw new InvalidDataException("The server port read was lower than the minimum value or greater than the maximum value that can be assigned to a port.");
            m_port = port;

            var clientVersion = reader.ReadString();
            if (String.IsNullOrWhiteSpace(clientVersion))
                throw new InvalidDataException("The client version read was null or empty, or if value consists exclusively of white-space characters.");
            m_clientVersion = clientVersion;

            var updateAddress = reader.ReadString();
            if (String.IsNullOrWhiteSpace(updateAddress))
                throw new InvalidDataException("The update address read was null or empty, or if value consists exclusively of white-space characters.");
            m_updateAddress = updateAddress;
        }

        #endregion

        #region Properties

        /// <summary>
        /// The connection address.
        /// </summary>
        internal string Address
        {
            get { return m_address; }
        }

        /// <summary>
        /// The connection port.
        /// </summary>
        /// <remarks>Port must be between <see cref="F:System.Net.IPEndPoint.MinPort" /> and <see cref="F:System.Net.IPEndPoint.MaxPort" />.</remarks>
        internal int Port
        {
            get { return m_port; }
        }

        /// <summary>
        /// The current version of the client software .
        /// </summary>
        internal string ClientVersion
        {
            get { return m_clientVersion; }
        }

        /// <summary>
        /// The address to use in order to update the client software.
        /// </summary>
        internal string UpdateAddress
        {
            get { return m_updateAddress; }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Define the invariant valid state for this class.
        /// </summary>
        [ContractInvariantMethod]
        private void InvariantValidState()
        {
            Contract.Invariant(!String.IsNullOrWhiteSpace(m_address));
            Contract.Invariant(m_port >= IPEndPoint.MinPort && m_port <= IPEndPoint.MaxPort);
            Contract.Invariant(!String.IsNullOrWhiteSpace(m_clientVersion));
            Contract.Invariant(!String.IsNullOrWhiteSpace(m_updateAddress));
        }

        /// <summary>
        /// Converts this instance to its equivalent string representation.
        /// </summary>
        /// <returns>The string representation of the value of this instance.</returns>
        public override string ToString()
        {
            return String.Format("address : {0} ; port : {1} ; client version : {2} ; update address : {2}", m_address, m_port, m_clientVersion, m_updateAddress);
        }

        /// <summary>
        /// Write the connection informations to the data writer.
        /// </summary>
        /// <param name="writer">Data writer.</param>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed.</exception>
        internal void Write(BinaryWriter writer)
        {
            Contract.Requires<ArgumentNullException>(writer != null);

            writer.Write(m_address);
            writer.Write(m_port);
            writer.Write(m_clientVersion);
            writer.Write(m_updateAddress);
        }

        #endregion
    }
}
